#!/usr/bin/env bash
shopt -s extglob

# *********************************************************************
# Copyright (C) 2019 Ben Lye

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# *********************************************************************

# Define the script version
VERSION=0.6.0

# Write the script header
printf "flash-multi $VERSION\n\nThis program is Free Software and has NO WARRANTY.\nhttps://github.com/benlye/flash-multi/\n\n";

# Prepare simple help text to display when needed
USAGE="Usage: flash-multi [options] -f [firmware file] -p [serial device]\n\nOptions:\n  -h  Print this message and exit\n  -d  Disable MCU flash check\n  -l  Install legacy bootloader\n  -r  Read and display the firmware file information and exit\n  -s  Don't prompt for confirmation\n  -b  Baud rate (57600, 115200, 500000)\n\n"

# Get the command line options
while getopts ":f:p:b:dhrsl" opt; do
  case $opt in
    f) FWFILE="$OPTARG"
    ;;
    p) PORT="$OPTARG"
    ;;
    h) printf "$USAGE"; exit 1
    ;;
    r) SHOWINFO="True"
    ;;
    l) LEGACY="True"
    ;;
    d) DISABLEMCUCHECK="True"
    ;;
    s) SILENT="True"
    ;;
    b) BAUDRATE="$OPTARG"
    ;;
    \?) printf "Invalid argument -$OPTARG\n\n"; >&2
    ;;
    :) printf "Missing value for argument -$OPTARG\n\n"; >&2
    ;;
  esac
done

# Show the usage and exit if no -f argument given
if [ "x" == "x$FWFILE" ]; then
  printf "$USAGE"
  exit 1
fi

# Die if the firmware file doesn't exist
if [ ! -f "$FWFILE" ]; then
    printf "ERROR: $FWFILE does not exist!\n\n";
    exit 2;
fi

# Die if the firmware file isn't readable
if [ ! -r "$FWFILE" ]; then
    printf "ERROR: $FWFILE cannot be read!\n\n";
    exit 2;
fi

# Die if the baud rate isn't valid
if [ "x" != "x$BAUDRATE" ] && [ $BAUDRATE != "57600" ] && [ $BAUDRATE != "115200" ] && [ $BAUDRATE != "500000" ] ; then
    printf "ERROR: $BAUDRATE is not a valid baud rate!\n\n";
    exit 2;
fi

# Set the baud rate for stm32flash
if [ "x" == "x$BAUDRATE" ]; then
  BAUDRATE=115200
fi

# Get the directory where the script is running.
DIR=$(dirname "$0")

# Determine the binaries to use based on the platform architecture
uname -a | grep "Darwin" 2>&1 1>/dev/null
if [ $? -eq 0 ]; then
  TOOLS_DIR="$DIR/tools/macos"
  GREP_ARGS=""
  STAT_ARGS="-f %z"
else
  uname -m | grep "x86_64" 2>&1 1>/dev/null
  if [ $? -eq 0 ]; then
    TOOLS_DIR="$DIR/tools/64bit"
  else
    TOOLS_DIR="$DIR/tools/32bit"
  fi

  GREP_ARGS="-Pa"
  STAT_ARGS="--printf=%s"
fi

AVRDUDE="$TOOLS_DIR/avrdude"
AVRDUDE_CONF="$TOOLS_DIR/avrdude.conf"
STM32_FLASH="$TOOLS_DIR/stm32flash"
RESET_UTIL="$TOOLS_DIR/maple-reset"
DFU_UTIL="$TOOLS_DIR/dfu-util"

function confirm() {
  while true; do
    read -p "$1 [Y]es or [N]o: "
    case $(echo $REPLY | tr '[A-Z]' '[a-z]') in
        y|yes) echo "yes"; return ;;
        n|no)  echo "no" ; return ;;
    esac
  done
}

function list_usb()
{
while IFS=: read key value; do
  key="${key##+( )}"
  value="${value##+( )}"
  case "$key" in
    "Product ID")
        p="${value% *}"
        ;;
    "Vendor ID")
        v="${value%% *}"
        ;;
    "Manufacturer")
        m="${value}"
        ;;
    "Location ID")
        l="${value}"
        printf "%s:%s %s (%s)\n" "$v" "$p" "$l" "$m"
        ;;
  esac
done < <( system_profiler SPUSBDataType )
}

function module_type()
{
  case "$1" in
    "0"|"avr")
      b="AVR"
      ;;
    "1"|"stm")
      b="STM32F1"
      ;;
    "2"|"orx")
      b="OrangeRX"
      ;;
    *)
      b="Unknown"
      ;;
  esac
  printf "$b"
}

function module_subtype()
{
  case "$1:$2" in
    "0:0")
      b="Atmega328p"
      ;;
    "1:0")
      b="STM32F103CB"
      ;;
    "1:1")
      b="STM32F103C8"
      ;;
    "1:2")
      b="Jumper T18 5-in-1"
      ;;
    "2:0")
      b="ATxmega32D4"
      ;;
    *)
      b="Unknown"
      ;;
  esac
  printf "$b"
}

function module_mcuflashsize()
{
  case "$1:$2" in
    "0:0")
      b=32
      ;;
    "1:0")
      b=128
      ;;
    "1:1")
      b=64
      ;;
    "1:2")
      b=128
      ;;
    "2:0")
      b=32
      ;;
    *)
      b=-1
      ;;
  esac
  printf "$b"
}

function telemetry_type()
{
  case "$1" in
    "0")
      b="Disabled"
      ;;
    "1")
      b="erSkyTx Only (MULTI_STATUS)"
      ;;
    "2")
      b="OpenTX or erSkyTx (MULTI_TELEMETRY)"
      ;;
    *)
      b="Unknown"
      ;;
  esac
  printf "$b"
}

channel_order_string()
{
  CHORDER=""
  if [ "$1" == "0" ]; then echo "AETR"; fi
  if [ "$1" == "1" ]; then echo "AERT"; fi
  if [ "$1" == "2" ]; then echo "ARET"; fi
  if [ "$1" == "3" ]; then echo "ARTE"; fi
  if [ "$1" == "4" ]; then echo "ATRE"; fi
  if [ "$1" == "5" ]; then echo "ATER"; fi
  if [ "$1" == "6" ]; then echo "EATR"; fi
  if [ "$1" == "7" ]; then echo "EART"; fi
  if [ "$1" == "8" ]; then echo "ERAT"; fi
  if [ "$1" == "9" ]; then echo "ERTA"; fi
  if [ "$1" == "10" ]; then echo "ETRA"; fi
  if [ "$1" == "11" ]; then echo "ETAR"; fi
  if [ "$1" == "12" ]; then echo "TEAR"; fi
  if [ "$1" == "13" ]; then echo "TERA"; fi
  if [ "$1" == "14" ]; then echo "TREA"; fi
  if [ "$1" == "15" ]; then echo "TRAE"; fi
  if [ "$1" == "16" ]; then echo "TARE"; fi
  if [ "$1" == "17" ]; then echo "TAER"; fi
  if [ "$1" == "18" ]; then echo "RETA"; fi
  if [ "$1" == "19" ]; then echo "REAT"; fi
  if [ "$1" == "20" ]; then echo "RAET"; fi
  if [ "$1" == "21" ]; then echo "RATE"; fi
  if [ "$1" == "22" ]; then echo "RTAE"; fi
  if [ "$1" == "23" ]; then echo "RTEA"; fi
  printf "$CHORDER"
}

function get_signature()
{
  FW_FILE=$1;
  FW_SIGNATURE=$(tail -c24 $FW_FILE);
  SIG_V1_REGEX="^multi-(stm|avr|orx)-(\\w{5})-([0-9]{8})$"
  SIG_V2_REGEX="^multi-x([0-9a-z]{8})-([0-9]{8})$"
  if [[ $FW_SIGNATURE =~ $SIG_V1_REGEX ]]; then
    SIG_BOARD=${BASH_REMATCH[1]}
    SIG_FLAGS=${BASH_REMATCH[2]}
    SIG_VERSION=${BASH_REMATCH[3]}

    SIG_BOARD_TYPE_NAME=$(module_type $SIG_BOARD);
    FW_CHANNEL_ORDER="Unknown"

    SIG_FLAG_REGEX="^(\\w)(\\w)(\\w)(\\w)(\\w)$"
    if [[ $SIG_FLAGS =~ $SIG_FLAG_REGEX ]]; then
      SIG_BOOTLOADER_FLAG=${BASH_REMATCH[1]}
      SIG_CHECK_FOR_BOOTLOADER_FLAG=${BASH_REMATCH[2]}
      SIG_TELEMETRY_TYPE_FLAG=${BASH_REMATCH[3]}
      SIG_TELEMETRY_INVERT_FLAG=${BASH_REMATCH[4]}
      SIG_DEBUG_SERIAL_FLAG=${BASH_REMATCH[5]}
    fi

    FW_BOOTLOADER_SUPPORT="False"
    if [[ "$SIG_BOOTLOADER_FLAG" == "b" ]]; then
      FW_BOOTLOADER_SUPPORT="True";
    fi

    FW_CHECK_FOR_BOOTLOADER_ENABLED="False"
    if [[ "$SIG_CHECK_FOR_BOOTLOADER_FLAG" == "c" ]]; then
      FW_CHECK_FOR_BOOTLOADER_ENABLED="True";
    fi

    FW_TELEMETRY_TYPE="Unknown"
    if [[ "$SIG_TELEMETRY_TYPE_FLAG" == "t" ]]; then
      FW_TELEMETRY_TYPE="OpenTX";
    elif [[ "$SIG_TELEMETRY_TYPE_FLAG" == "s" ]]; then
      FW_TELEMETRY_TYPE="erSkyTX";
    fi

    FW_INVERT_TELEMETRY_ENABLED="False"
    if [[ "$SIG_TELEMETRY_INVERT_FLAG" == "i" ]]; then
      FW_INVERT_TELEMETRY_ENABLED="True";
    fi

    FW_DEBUG_SERIAL_ENABLED="False"
    if [[ "$SIG_DEBUG_SERIAL_FLAG" == "d" ]]; then
      FW_DEBUG_SERIAL_ENABLED="True";
    fi

    SIG_VERSION_REGEX="^([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$"
    if [[ $SIG_VERSION =~ $SIG_VERSION_REGEX ]]; then
      SIG_VERSION_MAJOR=`printf "%d" ${BASH_REMATCH[1]}`
      SIG_VERSION_MINOR=`printf "%d" ${BASH_REMATCH[2]}`
      SIG_VERSION_REVISION=`printf "%d" ${BASH_REMATCH[3]}`
      SIG_VERSION_PATCH=`printf "%d" ${BASH_REMATCH[4]}`
      
      SIG_VERSION_STRING="$SIG_VERSION_MAJOR.$SIG_VERSION_MINOR.$SIG_VERSION_REVISION.$SIG_VERSION_PATCH"
    fi
  elif [[ $FW_SIGNATURE =~ $SIG_V2_REGEX ]]; then
    SIG_FLAGS_HEX=${BASH_REMATCH[1]}
    SIG_VERSION=${BASH_REMATCH[2]}

    # Get the module type
    SIG_MODULE_TYPE="$(( 0x$SIG_FLAGS_HEX & 0x3 ))"
    SIG_BOARD_TYPE_NAME=$(module_type $SIG_MODULE_TYPE);

    # Get the module sub-type
    SIG_MODULE_SUBTYPE="$(( (0x$SIG_FLAGS_HEX & 0xE000) >> 13 ))"
    SIG_MODULE_SUBTYPE_NAME=$(module_subtype $SIG_MODULE_TYPE $SIG_MODULE_SUBTYPE);

    # Get the module flash size
    SIG_MODULE_FLASH_SIZE=$(module_mcuflashsize $SIG_MODULE_TYPE $SIG_MODULE_SUBTYPE);

    # Get the channel order
    SIG_CHANNEL_ORDER="$(( (0x$SIG_FLAGS_HEX & 0x7C) >> 2 ))"
    FW_CHANNEL_ORDER=$(channel_order_string $SIG_CHANNEL_ORDER)

    # Set the bitwise flag values
    [[ "$(( 0x$SIG_FLAGS_HEX & 0x80 ))" -gt 0 ]] && FW_BOOTLOADER_SUPPORT="True" || FW_BOOTLOADER_SUPPORT="False"
    [[ "$(( 0x$SIG_FLAGS_HEX & 0x100 ))" -gt 0 ]] && FW_CHECK_FOR_BOOTLOADER_ENABLED="True" || FW_CHECK_FOR_BOOTLOADER_ENABLED="False"
    [[ "$(( 0x$SIG_FLAGS_HEX & 0x200 ))" -gt 0 ]] && FW_INVERT_TELEMETRY_ENABLED="True" || FW_INVERT_TELEMETRY_ENABLED="False"
    [[ "$(( 0x$SIG_FLAGS_HEX & 0x400 ))" -gt 0 ]] && FW_MULTI_STATUS_ENABLED=1 || FW_MULTI_STATUS_ENABLED=0
    [[ "$(( 0x$SIG_FLAGS_HEX & 0x800 ))" -gt 0 ]] && FW_MULTI_TELEMETRY_ENABLED=1 || FW_MULTI_TELEMETRY_ENABLED=0
    [[ "$(( 0x$SIG_FLAGS_HEX & 0x1000 ))" -gt 0 ]] && FW_DEBUG_SERIAL_ENABLED="True" || FW_DEBUG_SERIAL_ENABLED="False"

    # Set the Multi Telemetry type
    SIG_TELEMETRY_TYPE="$(( (0x$SIG_FLAGS_HEX & 0xC00) >> 10 ))"
    FW_TELEMETRY_TYPE=$(telemetry_type $SIG_TELEMETRY_TYPE)

    # Assemble the version number string
    SIG_VERSION_REGEX="^([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$"
    if [[ $SIG_VERSION =~ $SIG_VERSION_REGEX ]]; then
      SIG_VERSION_MAJOR=`printf "%d" ${BASH_REMATCH[1]}`
      SIG_VERSION_MINOR=`printf "%d" ${BASH_REMATCH[2]}`
      SIG_VERSION_REVISION=`printf "%d" ${BASH_REMATCH[3]}`
      SIG_VERSION_PATCH=`printf "%d" ${BASH_REMATCH[4]}`
      
      SIG_VERSION_STRING="$SIG_VERSION_MAJOR.$SIG_VERSION_MINOR.$SIG_VERSION_REVISION.$SIG_VERSION_PATCH"
    fi
  else
    printf "Firmware signature not found - unable to validate firmware.\n\n";
    return;
  fi

  # Determine if the specified firmware file contains support for the STM32 USB port
  if grep -q $GREP_ARGS 'M\x00a\x00p\x00l\x00e\x00\x12\x03L\x00e\x00a\x00f\x00L\x00a\x00b\x00s' "$FWFILE"; then
    FW_USBSERIAL_SUPPORT="True"
  else
    FW_USBSERIAL_SUPPORT="False"
  fi

  # Show the firmware information
  printf "Firmware File Name:        $(basename $FWFILE)\n";
  printf "Multi Firmware Version:    $SIG_VERSION_STRING ($SIG_BOARD_TYPE_NAME)\n";
  printf "Firmware Target:           $SIG_MODULE_SUBTYPE_NAME (${SIG_MODULE_FLASH_SIZE}KB)\n"
  printf "Expected Channel Order:    $FW_CHANNEL_ORDER\n";
  printf "Multi Telemetry Type:      $FW_TELEMETRY_TYPE\n";
  printf "Invert Telemetry Enabled:  $FW_INVERT_TELEMETRY_ENABLED\n";
  printf "Flash From Radio Enabled:  $FW_CHECK_FOR_BOOTLOADER_ENABLED\n";
  printf "Bootloader Enabled:        $FW_BOOTLOADER_SUPPORT\n";
  printf "USB Serial Support:        $FW_USBSERIAL_SUPPORT\n";
  printf "Serial Debug Enabled:      $FW_DEBUG_SERIAL_ENABLED\n\n";
}

# Just show the info and quit if -r was specified
if [[ $SHOWINFO == "True" ]]; then
  get_signature $FWFILE
  exit 0
fi

# Check to see if a native STM32 USB device (Maple device) or USBasp is attached
uname -a | grep "Darwin" 2>&1 1>/dev/null
if [ $? -eq 0 ]; then
  MAPLE_USB_DEVICES=$(echo "$(list_usb)" | grep "1eaf" | awk '{ print $1}')
  USBASP_DEVICE=$(echo "$(list_usb)" | grep "0x16c0:0x05dc" | awk '{ print $1}')
else
  MAPLE_USB_DEVICES=$(lsusb | grep "1eaf")
  USBASP_DEVICE=$(lsusb | grep "16c0:05dc")
fi

case $MAPLE_USB_DEVICES in 
   *?(0x)1eaf:?(0x)0003*)
      MAPLE_DEVICE=DFU
   ;;
   *?(0x)1eaf:?(0x)0004*)
      MAPLE_DEVICE=Native
   ;;
   *)
      MAPLE_DEVICE=Unknown
   ;;
esac

# Check if port exists for Maple USB or serial - there is no port needed for DFU or USBasp
if [ $MAPLE_DEVICE == "Native" ] || ([ $MAPLE_DEVICE == "Unknown" ] && [ "x$USBASP_DEVICE" == "x" ]) ; then
  # Die if a serial port wasn't specified
  if [ "x" == "x$PORT" ]; then
    printf "ERROR: No port specified and no programmable device found!\n\n";
    printf "$USAGE"
    exit 1
  fi

  # Die if the serial port doesn't exist
  if [ ! -c "$PORT" ]; then
      printf "ERROR: $PORT does not exist!\n\n";
      exit 3;
  fi
fi

# Display information about the firmware file
get_signature $FWFILE

# Ask the user to confirm
if [[ $SILENT != "True" ]]; then
  if [[ "no" == $(confirm "Proceed with firmware update?") ]]
  then
      printf "\nFirmware update aborted.\n\n"
      exit 0
  fi
fi
echo

# AVR Module Flashing
if [[ $SIG_BOARD_TYPE_NAME == "AVR" ]]; then
  BOOTLOADERFILE="$DIR/bootloader/AtmegaMultiBoot.hex"

  # Die if there's no USBasp device
  if [ "x$USBASP_DEVICE" == "x" ] ; then
    printf "ERROR: USBasp device not found!\n\n"; exit 6;
  fi

  # Die if AVRDUDE can't be found
  if [ ! -x $AVRDUDE ]; then
    printf "ERROR: Required tool $AVRDUDE does not exist or is not executable!\n\n"; exit 3;
  fi

  if [[ "$(( 0x$SIG_FLAGS_HEX & 0x80 ))" -gt 0 ]]; then
    printf "Writing firmware (including bootloader)...\n"
    printf "$AVRDUDE -C$AVRDUDE_CONF -patmega328p -cusbasp -Ulock:w:0x3F:m -Uefuse:w:0xFD:m -Uhfuse:w:0xD6:m -Ulfuse:w:0xFF:m \"-Uflash:w:$BOOTLOADERFILE:i\" \"-Uflash:w:$FWFILE:a\" -Ulock:w:0x0F:m\n"
    "$AVRDUDE" -C"$AVRDUDE_CONF" -patmega328p -cusbasp -Ulock:w:0x3F:m -Uefuse:w:0xFD:m -Uhfuse:w:0xD6:m -Ulfuse:w:0xFF:m "-Uflash:w:$BOOTLOADERFILE:i" "-Uflash:w:$FWFILE:a" -Ulock:w:0x0F:m
    [ $? -ne 0 ] && printf "\nERROR: Failed to write firmware!\n\n" && exit 5;
  else
    printf "Writing firmware...\n"
    printf "$AVRDUDE -C$AVRDUDE_CONF -patmega328p -cusbasp -Ulock:w:0x3F:m  -Uefuse:w:0xFD:m -Uhfuse:w:0xD7:m -Ulfuse:w:0xFF:m \"-Uflash:w:$FWFILE:a\"\n"
    "$AVRDUDE" -C"$AVRDUDE_CONF" -patmega328p -cusbasp -Ulock:w:0x3F:m -Uefuse:w:0xFD:m -Uhfuse:w:0xD7:m -Ulfuse:w:0xFF:m "-Uflash:w:$FWFILE:a"
    [ $? -ne 0 ] && printf "\nERROR: Failed to write firmware!\n\n" && exit 5;
  fi
  printf "Firmware flashed successfully.\n\n"
  exit 0
fi

# STM32 Module Flashing
# Set the path to the bootloader file
if [[ $LEGACY == "True" ]]; then
  BOOTLOADERFILE="$DIR/bootloader/StmMulti4in1_Legacy.bin"
else
  BOOTLOADERFILE="$DIR/bootloader/StmMulti4in1_StickyDfu.bin"
fi

# Set the parameters according to bootloader support
if [ $FW_BOOTLOADER_SUPPORT == "True" ]; then
  MAX_FILE_SIZE=120832
  FLASH_START=8
  EXEC_ADDR=0x8002000
else
  MAX_FILE_SIZE=129024
  FLASH_START=0
  EXEC_ADDR=0x8000000
fi

# Set EEPROM end address according to module flash size
ERASE_END=$(( ($SIG_MODULE_FLASH_SIZE * 1024) - 2048 ));

# Check if the firmware will fit
FWFILE_SIZE=`stat $STAT_ARGS "$FWFILE"`
if [ $FWFILE_SIZE -gt $MAX_FILE_SIZE ]; then
    printf "ERROR: Firmware file is too large. File is $FWFILE_SIZE, maximum is $MAX_FILE_SIZE.\n\n";
    exit 2;
fi

# Run the Maple or serial flash
if [[ $MAPLE_DEVICE == "Unknown" ]]
then
  # No Maple device - run the serial flash
  printf "Attempting serial upload using stm32flash\n\n"
  
  if [ ! -x $STM32_FLASH ]; then
    printf "ERROR: Required tool $STM32_FLASH does not exist or is not executable!\n\n"; exit 3;
  fi
  
  STEP=1
  [ $FW_BOOTLOADER_SUPPORT == "True" ] && NUM_STEPS=3 || NUM_STEPS=2;
  
  printf "[$STEP/$NUM_STEPS] Erasing flash...\n"
  printf "$STM32_FLASH -o -S 0x8000000:$ERASE_END -b $BAUDRATE \"$PORT\"\n"
  "${STM32_FLASH}" -o -S 0x8000000:$ERASE_END -b $BAUDRATE "$PORT"
  [ $? -ne 0 ] && printf "ERROR: Failed to erase flash!\n\n" && exit 3;
  STEP=$((STEP+1))
  
  if [ $FW_BOOTLOADER_SUPPORT == "True" ] ; then
    printf "[$STEP/$NUM_STEPS] Writing bootloader...\n"
    printf "$STM32_FLASH -v -e 0 -g 0x8000000 -b $BAUDRATE -w \"$BOOTLOADERFILE\" \"$PORT\"\n"
    "${STM32_FLASH}" -v -e 0 -g 0x8000000 -b $BAUDRATE -w "$BOOTLOADERFILE" "$PORT"
    [ $? -ne 0 ] && printf "ERROR: Failed to write bootloader!\n\n" && exit 3;
    STEP=$((STEP+1))
  fi
  
  printf "[$STEP/$NUM_STEPS] Writing firmware...\n"
  printf "$STM32_FLASH -v -s $FLASH_START -e 0 -g $EXEC_ADDR -b $BAUDRATE -w \"$FWFILE\" \"$PORT\"\n"
  "${STM32_FLASH}" -v -s $FLASH_START -e 0 -g $EXEC_ADDR -b $BAUDRATE -w "$FWFILE" "$PORT"
  [ $? -ne 0 ] && printf "ERROR: Failed to write firmware!\n\n" && exit 3;
    
else
  # Maple device detected
  printf "Attempting USB upload using dfu-util\n\n"

  if [ ! -x $RESET_UTIL ]; then
    printf "ERROR: Required tool $RESET_UTIL does not exist or is not executable!\n\n"; exit 3;
  fi
  
  if [ ! -x $DFU_UTIL ]; then
    printf "ERROR: Required tool $DFU_UTIL does not exist or is not executable!\n\n"; exit 3;
  fi
  
  # Warn if the firmware file doesn't contain USB code
  if [ $FW_USBSERIAL_SUPPORT == "False" ]; then
    printf "WARNING: Specified firmware file was not compiled with USB support.\n";
    printf "         You MUST update the MULTI-module bootloader to the latest version BEFORE writing this firmware.\n\n"; 
    printf "         See https://github.com/benlye/flash-multi/blob/master/doc/New_Bootloader.md for more information.\n\n";

    if [[ "no" == $(confirm "Proceed with firmware update?") ]]; then
        printf "\nFirmware update aborted.\n\n"
        exit 9;
    fi
  fi
  
  STEP=1;
  NUM_STEPS=1;

  if [[ $MAPLE_DEVICE == "Native" ]]; then NUM_STEPS=$((NUM_STEPS+1)); fi;
  if [[ ! $DISABLEMCUCHECK == "True" ]]; then NUM_STEPS=$((NUM_STEPS+1)); fi;

  if [[ $MAPLE_DEVICE == "Native" ]] ; then
    printf "[$STEP/$NUM_STEPS] Resetting to DFU mode...\n"
    printf "$RESET_UTIL $PORT\n";
    "${RESET_UTIL}" $PORT;
    [ $? -ne 0 ] && printf "ERROR: Failed to reset device!\n\n" && exit 4;
    
    # Give the board a chance to reset to DFU mode
    sleep 1
    
    STEP=$((STEP+1))
  fi
  
  if [[ $DISABLEMCUCHECK != "True" ]]; then
    TEMPFILE=$(mktemp);
    printf "[$STEP/$NUM_STEPS] Checking flash size ...\n";
    printf "${DFU_UTIL} -a 2 -d 1EAF:0003 -U \"$TEMPFILE\"\n";
    "${DFU_UTIL}" -a 2 -d 1EAF:0003 -U "$TEMPFILE";

    TEMPFILE_SIZE=`stat $STAT_ARGS "$TEMPFILE"`
    if [ $TEMPFILE_SIZE -eq 0 ]; then
      printf "\nERROR: MCU Flash size verification failed!\n\n";
      exit 6;
    fi

    EXPECTED_SIZE=$(( ($SIG_MODULE_FLASH_SIZE * 1024) - 8192 ))
    if [ $TEMPFILE_SIZE -lt $EXPECTED_SIZE ]; then
      printf "\nERROR: MCU Flash size is too small!\n\n";
      exit 7;
    fi

    printf "\n";
    STEP=$((STEP+1))
  fi

  printf "[$STEP/$NUM_STEPS] Writing firmware...\n"
  printf "${DFU_UTIL} -d 1eaf:0003 -a 2 -D \"$FWFILE\" -R\n";
  "${DFU_UTIL}" -d 1eaf:0003 -a 2 -D "$FWFILE" -R;
  [ $? -ne 0 ] && printf "\nERROR: Failed to write firmware!\n\n" && exit 5;
  printf "\n";
fi

printf "MULTI-Module firmware flashed successfully.\n\n"
